// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

.intel_syntax noprefix
#include <AsmOffsets.inc>         // generated by the build from AsmOffsets.cpp
#include <unixasmmacros.inc>

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RhpThrowHwEx
//
// INPUT:  RDI:  exception code of fault
//         RSI:  faulting RIP
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpThrowHwEx, _TEXT, NoHandler

        STACKSIZEOF_ExInfo      = ((SIZEOF__ExInfo + 15) & (~15))
        rsp_offsetof_Context    = STACKSIZEOF_ExInfo

        mov     rax, rsp        // save the faulting RSP

        // Align the stack towards zero
        and     rsp, -16

        xor     rdx, rdx

//  struct PAL_LIMITED_CONTEXT
//  {
        push_nonvol_reg r15
        push_nonvol_reg r14
        push_nonvol_reg r13
        push_nonvol_reg r12
        push_register   rdx             // rdx set to 0
        push_nonvol_reg rbx
        push_register   rdx             // rax set to 0
        push_nonvol_reg rbp
        push_register   rax             // faulting RSP
        push_register   rsi             // faulting IP
//  }
        // allocate outgoing args area and space for the ExInfo
        alloc_stack     STACKSIZEOF_ExInfo

        END_PROLOGUE

        mov     rbx, rdi
        INLINE_GETTHREAD
        mov     rdi, rbx

        mov     rsi, rsp                                             // rsi <- ExInfo*

        xor     rdx, rdx
        mov     [rsi + OFFSETOF__ExInfo__m_exception], rdx           // init the exception object to null
        mov     byte ptr [rsi + OFFSETOF__ExInfo__m_passNumber], 1   // init to the first pass
        mov     dword ptr [rsi + OFFSETOF__ExInfo__m_idxCurClause], 0xFFFFFFFF
        mov     byte ptr [rsi + OFFSETOF__ExInfo__m_kind], 2         // ExKind.HardwareFault

        // link the ExInfo into the thread's ExInfo chain
        mov     rdx, [rax + OFFSETOF__Thread__m_pExInfoStackHead]
        mov     [rsi + OFFSETOF__ExInfo__m_pPrevExInfo], rdx         // pExInfo->m_pPrevExInfo = m_pExInfoStackHead
        mov     [rax + OFFSETOF__Thread__m_pExInfoStackHead], rsi    // m_pExInfoStackHead = pExInfo

        // set the exception context field on the ExInfo
        lea     rdx, [rsp + rsp_offsetof_Context]                    // rdx <- PAL_LIMITED_CONTEXT*
        mov     [rsi + OFFSETOF__ExInfo__m_pExContext], rdx          // init ExInfo.m_pExContext

        // rdi still contains the exception code
        // rsi contains the address of the ExInfo
        call    EXTERNAL_C_FUNC(RhThrowHwEx)

ALTERNATE_ENTRY RhpThrowHwEx2

        // no return
        int 3

NESTED_END RhpThrowHwEx, _TEXT


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RhpThrowEx
//
// INPUT:  RDI:  exception object
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpThrowEx, _TEXT, NoHandler

        STACKSIZEOF_ExInfo      = ((SIZEOF__ExInfo + 15) & (~ 15))
        rsp_offsetof_Context    = STACKSIZEOF_ExInfo

        lea     rax, [rsp+8]    // save the RSP of the throw site
        mov     rsi, [rsp]      // get return address

        xor     rdx, rdx
        push_register   rdx             // padding

//  struct PAL_LIMITED_CONTEXT
//  {
        push_nonvol_reg r15
        push_nonvol_reg r14
        push_nonvol_reg r13
        push_nonvol_reg r12
        push_register   rdx             // rdx set to 0
        push_nonvol_reg rbx
        push_register   rdx             // rax set to 0
        push_nonvol_reg rbp
        push_register   rax             // 'faulting' RSP
        push_register   rsi             // 'faulting' IP
//  }

        // allocate space for the ExInfo
        alloc_stack     STACKSIZEOF_ExInfo

        END_PROLOGUE

        mov     rbx, rdi
        INLINE_GETTHREAD
        mov     rdi, rbx

        lea                     rbx, [rsp + rsp_offsetof_Context + SIZEOF__PAL_LIMITED_CONTEXT + 0x8]    // rbx <- addr of return address

        // There is runtime C# code that can tail call to RhpThrowEx using a binder intrinsic.  So the return
        // address could have been hijacked when we were in that C# code and we must remove the hijack and
        // reflect the correct return address in our exception context record.  The other throw helpers don't
        // need this because they cannot be tail-called from C#.
        INLINE_THREAD_UNHIJACK  rax, rcx, rsi        // trashes RCX, RSI
        mov                     rsi, [rbx]           // rdx <- return address
        mov                     [rsp + rsp_offsetof_Context + OFFSETOF__PAL_LIMITED_CONTEXT__IP], rsi   // set 'faulting' IP after unhijack

        mov     rsi, rsp                                            // rsi <- ExInfo*

        mov     [rsi + OFFSETOF__ExInfo__m_exception], rdx          // init the exception object to null
        mov     byte ptr [rsi + OFFSETOF__ExInfo__m_passNumber], 1  // init to the first pass
        mov     dword ptr [rsi + OFFSETOF__ExInfo__m_idxCurClause], 0xFFFFFFFF
        mov     byte ptr [rsi + OFFSETOF__ExInfo__m_kind], 1        // ExKind.Throw

        // link the ExInfo into the thread's ExInfo chain
        mov     rdx, [rax + OFFSETOF__Thread__m_pExInfoStackHead]
        mov     [rsi + OFFSETOF__ExInfo__m_pPrevExInfo], rdx        // pExInfo->m_pPrevExInfo = m_pExInfoStackHead
        mov     [rax + OFFSETOF__Thread__m_pExInfoStackHead], rsi   // m_pExInfoStackHead = pExInfo

        // set the exception context field on the ExInfo
        lea     rdx, [rsp + rsp_offsetof_Context]                   // rdx <- PAL_LIMITED_CONTEXT*
        mov     [rsi + OFFSETOF__ExInfo__m_pExContext], rdx         // init ExInfo.m_pExContext

        // rdi still contains the exception object
        // rsi contains the address of the ExInfo
        call    EXTERNAL_C_FUNC(RhThrowEx)

ALTERNATE_ENTRY RhpThrowEx2

        // no return
        int 3

NESTED_END RhpThrowEx, _TEXT

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void FASTCALL RhpRethrow()
//
// SUMMARY:  Similar to RhpThrowEx, except that it passes along the currently active ExInfo
//
// INPUT:
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpRethrow, _TEXT, NoHandler

        STACKSIZEOF_ExInfo      = ((SIZEOF__ExInfo + 15) & (~ 15))
        rsp_offsetof_Context    = STACKSIZEOF_ExInfo

        lea     rax, [rsp+8]    // save the RSP of the throw site
        mov     rsi, [rsp]      // get return address

        xor     rdx, rdx
        push_register   rdx             // padding

//  struct PAL_LIMITED_CONTEXT
//  {
        push_nonvol_reg r15
        push_nonvol_reg r14
        push_nonvol_reg r13
        push_nonvol_reg r12
        push_register   rdx             // rdx set to 0
        push_nonvol_reg rbx
        push_register   rdx             // rax set to 0
        push_nonvol_reg rbp
        push_register   rax             // 'faulting' RSP
        push_register   rsi             // 'faulting' IP
//  }

        // allocate space for the ExInfo
        alloc_stack     STACKSIZEOF_ExInfo

        END_PROLOGUE

        INLINE_GETTHREAD

        mov     rsi, rsp                                            // rsi <- ExInfo*

        mov     [rsi + OFFSETOF__ExInfo__m_exception], rdx          // init the exception object to null
        mov     byte ptr [rsi + OFFSETOF__ExInfo__m_passNumber], 1  // init to the first pass
        mov     dword ptr [rsi + OFFSETOF__ExInfo__m_idxCurClause], 0xFFFFFFFF
        mov     byte ptr [rsi + OFFSETOF__ExInfo__m_kind], 0        // init to a deterministic value (ExKind.None)


        // link the ExInfo into the thread's ExInfo chain
        mov     rdi, [rax + OFFSETOF__Thread__m_pExInfoStackHead]   // rdi <- currently active ExInfo
        mov     [rsi + OFFSETOF__ExInfo__m_pPrevExInfo], rdi        // pExInfo->m_pPrevExInfo = m_pExInfoStackHead
        mov     [rax + OFFSETOF__Thread__m_pExInfoStackHead], rsi   // m_pExInfoStackHead = pExInfo

        // set the exception context field on the ExInfo
        lea     rdx, [rsp + rsp_offsetof_Context]                   // rdx <- PAL_LIMITED_CONTEXT*
        mov     [rsi + OFFSETOF__ExInfo__m_pExContext], rdx         // init ExInfo.m_pExContext

        // rdi contains the currently active ExInfo
        // rsi contains the address of the new ExInfo
        call    EXTERNAL_C_FUNC(RhRethrow)

ALTERNATE_ENTRY RhpRethrow2

        // no return
        int 3

NESTED_END RhpRethrow, _TEXT

//
// Prologue of all funclet calling helpers (RhpCallXXXXFunclet)
//
.macro FUNCLET_CALL_PROLOGUE localsCount, alignStack

    push_nonvol_reg r15     // save preserved regs for OS stackwalker
    push_nonvol_reg r14     // ...
    push_nonvol_reg r13     // ...
    push_nonvol_reg r12     // ...
    push_nonvol_reg rbx     // ...
    push_nonvol_reg rbp     // ...

    stack_alloc_size = \localsCount * 8 + \alignStack * 8

    alloc_stack     stack_alloc_size

    END_PROLOGUE
.endm

//
// Epilogue of all funclet calling helpers (RhpCallXXXXFunclet)
//
.macro FUNCLET_CALL_EPILOGUE
    free_stack      stack_alloc_size

    pop_nonvol_reg rbp
    pop_nonvol_reg rbx
    pop_nonvol_reg r12
    pop_nonvol_reg r13
    pop_nonvol_reg r14
    pop_nonvol_reg r15
.endm

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void* FASTCALL RhpCallCatchFunclet(OBJECTREF exceptionObj, void* pHandlerIP, REGDISPLAY* pRegDisplay,
//                                    ExInfo* pExInfo)
//
// INPUT:  RDI:  exception object
//         RSI:  handler funclet address
//         RDX:   REGDISPLAY*
//         RCX:   ExInfo*
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpCallCatchFunclet, _TEXT, NoHandler

        FUNCLET_CALL_PROLOGUE 6, 1

        locThread       = 0
        locResumeIp     = 8
        locArg0         = 0x10
        locArg1         = 0x18
        locArg2         = 0x20
        locArg3         = 0x28

        mov     [rsp + locArg0], rdi                                // save arguments for later
        mov     [rsp + locArg1], rsi
        mov     [rsp + locArg2], rdx
        mov     [rsp + locArg3], rcx

        mov     rbx, rdx
        INLINE_GETTHREAD
        mov     rdx, rbx

        mov     [rsp + locThread], rax                              // save Thread* for later

        // Clear the DoNotTriggerGc state before calling out to our managed catch funclet.
   lock and     dword ptr [rax + OFFSETOF__Thread__m_ThreadStateFlags], ~TSF_DoNotTriggerGc

        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbx]
        mov     rbx, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     rbp, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR12]
        mov     r12, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR13]
        mov     r13, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR14]
        mov     r14, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR15]
        mov     r15, [rax]

#if 0 // _DEBUG  // @TODO: temporarily removed because trashing RBP breaks the debugger
        // trash the values at the old homes to make sure nobody uses them
        mov     rcx, 0xbaaddeed
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbx]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR12]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR13]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR14]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR15]
        mov     [rax], rcx
#endif

        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__SP]               // rdi <- establisher frame
        mov     rsi, [rsp + locArg0]                                // rsi <- exception object
        call    qword ptr [rsp + locArg1]                           // call handler funclet

ALTERNATE_ENTRY RhpCallCatchFunclet2

        mov     rdx, [rsp + locArg2]                                // rdx <- dispatch context

#ifdef _DEBUG
        // Call into some C++ code to validate the pop of the ExInfo.  We only do this in debug because we
        // have to spill all the preserved registers and then refill them after the call.

        mov     [rsp + locResumeIp], rax                            // save resume IP for later

        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pRbx]
        mov     [rdi]                            , rbx
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     [rdi]                            , rbp
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pR12]
        mov     [rdi]                            , r12
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pR13]
        mov     [rdi]                            , r13
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pR14]
        mov     [rdi]                            , r14
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pR15]
        mov     [rdi]                            , r15

        mov     rdi, [rsp]                                          // rdi <- Thread*
        mov     rsi, [rsp + locArg3]                                // rsi <- current ExInfo *
        mov     rdx, [rdx + OFFSETOF__REGDISPLAY__SP]               // rdx  <- resume SP value
        call    C_FUNC(RhpValidateExInfoPop)

        mov     rdx, [rsp + locArg2]          // rdx <- dispatch context
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbx]
        mov     rbx, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     rbp, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR12]
        mov     r12, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR13]
        mov     r13, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR14]
        mov     r14, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR15]
        mov     r15, [rax]

        mov     rax, [rsp + locResumeIp]                            // reload resume IP
#endif
        mov     rsi, [rsp + locThread]                              // rsi <- Thread*

        // We must unhijack the thread at this point because the section of stack where the hijack is applied
        // may go dead.  If it does, then the next time we try to unhijack the thread, it will corrupt the stack.
        INLINE_THREAD_UNHIJACK rsi, rdi, rcx                        // Thread in rsi, trashes rdi and rcx

        mov     rdi, [rsp + locArg3]                                // rdi <- current ExInfo *
        mov     rdx, [rdx + OFFSETOF__REGDISPLAY__SP]               // rdx <- resume SP value
        xor     ecx, ecx                                            // rcx <- 0

LOCAL_LABEL(ExInfoLoop):
        mov     rdi, [rdi + OFFSETOF__ExInfo__m_pPrevExInfo]        // rdi <- next ExInfo
        cmp     rdi, rcx
        je      LOCAL_LABEL(ExInfoLoopDone)                         // we're done if it's null
        cmp     rdi, rdx
        jl      LOCAL_LABEL(ExInfoLoop)                             // keep looping if it's lower than the new SP

LOCAL_LABEL(ExInfoLoopDone):
        mov     [rsi + OFFSETOF__Thread__m_pExInfoStackHead], rdi   // store the new head on the Thread

        // reset RSP and jump to the continuation address
        mov     rsp, rdx                                            // reset the SP
        jmp     rax

NESTED_END RhpCallCatchFunclet, _TEXT

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void FASTCALL RhpCallFinallyFunclet(void* pHandlerIP, REGDISPLAY* pRegDisplay)
//
// INPUT:  RDI:  handler funclet address
//         RSI:  REGDISPLAY*
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpCallFinallyFunclet, _TEXT, NoHandler

        FUNCLET_CALL_PROLOGUE 3, 0

        locThread       = 0
        locArg0         = 8
        locArg1         = 0x10

        mov     [rsp + locArg0], rdi                                 // save arguments for later
        mov     [rsp + locArg1], rsi

        mov     rbx, rsi
        INLINE_GETTHREAD
        mov     rsi, rbx

        mov     [rsp + locThread], rax                               // save Thread* for later

        //
        // We want to suppress hijacking between invocations of subsequent finallys.  We do this because we
        // cannot tolerate a GC after one finally has run (and possibly side-effected the GC state of the
        // method) and then been popped off the stack, leaving behind no trace of its effect.
        //
        // So we clear the state before and set it after invocation of the handler.
        //
   lock and     dword ptr [rax + OFFSETOF__Thread__m_ThreadStateFlags], ~TSF_DoNotTriggerGc

        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pRbx]
        mov     rbx, [rax]
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pRbp]
        mov     rbp, [rax]
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR12]
        mov     r12, [rax]
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR13]
        mov     r13, [rax]
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR14]
        mov     r14, [rax]
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR15]
        mov     r15, [rax]

#if 0 // _DEBUG // @TODO: temporarily removed because trashing RBP breaks the debugger
        // trash the values at the old homes to make sure nobody uses them
        mov     rcx, 0xbaaddeed
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pRbx]
        mov     [rax], rcx
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pRbp]
        mov     [rax], rcx
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR12]
        mov     [rax], rcx
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR13]
        mov     [rax], rcx
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR14]
        mov     [rax], rcx
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR15]
        mov     [rax], rcx
#endif

        mov     rdi, [rsi + OFFSETOF__REGDISPLAY__SP]                // rdi <- establisher frame
        call    qword ptr [rsp + locArg0]                            // handler funclet address

ALTERNATE_ENTRY RhpCallFinallyFunclet2

        mov     rsi, [rsp + locArg1]                                 // rsi <- regdisplay

        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pRbx]
        mov     [rax]                            , rbx
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pRbp]
        mov     [rax]                            , rbp
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR12]
        mov     [rax]                            , r12
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR13]
        mov     [rax]                            , r13
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR14]
        mov     [rax]                            , r14
        mov     rax, [rsi + OFFSETOF__REGDISPLAY__pR15]
        mov     [rax]                            , r15

        mov     rax, [rsp + locThread]                               // rax <- Thread*
   lock or      dword ptr [rax + OFFSETOF__Thread__m_ThreadStateFlags], TSF_DoNotTriggerGc

        FUNCLET_CALL_EPILOGUE

        ret

NESTED_END RhpCallFinallyFunclet, _TEXT


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void* FASTCALL RhpCallFilterFunclet(OBJECTREF exceptionObj, void* pFilterIP, REGDISPLAY* pRegDisplay)
//
// INPUT:  RDI:  exception object
//         RSI:  filter funclet address
//         RDX:  REGDISPLAY*
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpCallFilterFunclet, _TEXT, NoHandler

        FUNCLET_CALL_PROLOGUE 0, 1

        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     rbp, [rax]

        mov     rax, rsi                                            // rax <- handler funclet address
        mov     rsi, rdi                                            // rsi <- exception object
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__SP]               // rdi <- establisher frame
        call    rax

ALTERNATE_ENTRY RhpCallFilterFunclet2

        // RAX contains the result of the filter execution

        FUNCLET_CALL_EPILOGUE

        ret

NESTED_END RhpCallFilterFunclet, _TEXT


#ifdef FEATURE_OBJCMARSHAL

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// void* FASTCALL RhpCallPropagateExceptionCallback(void* pCallbackContext, void* pCallback, REGDISPLAY* pRegDisplay,
//                                    ExInfo* pExInfo, PInvokeTransitionFrame* pPreviousTransitionFrame)
//
// INPUT:  RDI:  callback context
//         RSI:  callback
//         RDX:  REGDISPLAY*
//         RCX:  ExInfo*
//         R8 :  pPreviousTransitionFrame
//
// OUTPUT:
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpCallPropagateExceptionCallback, _TEXT, NoHandler

        // We could use a simpler prolog, as we don't have to be unwindable by StackFrameIterator::UnwindFuncletInvokeThunk()
        FUNCLET_CALL_PROLOGUE 6, 1

        locThread       = 0
        locArg0         = 0x08
        locArg1         = 0x10
        locArg2         = 0x18
        locArg3         = 0x20
        locArg4         = 0x28

        mov     [rsp + locArg0], rdi                                // save arguments for later
        mov     [rsp + locArg1], rsi
        mov     [rsp + locArg2], rdx
        mov     [rsp + locArg3], rcx
        mov     [rsp + locArg4], r8

        mov     rbx, rdx
        INLINE_GETTHREAD
        mov     rdx, rbx

        mov     [rsp + locThread], rax                              // save Thread* for later

        // Clear the DoNotTriggerGc state before calling out to the propagation callback.
   lock and     dword ptr [rax + OFFSETOF__Thread__m_ThreadStateFlags], ~TSF_DoNotTriggerGc

        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbx]
        mov     rbx, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     rbp, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR12]
        mov     r12, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR13]
        mov     r13, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR14]
        mov     r14, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR15]
        mov     r15, [rax]

#if 0 // _DEBUG  // @TODO: temporarily removed because trashing RBP breaks the debugger
        // trash the values at the old homes to make sure nobody uses them
        mov     rcx, 0xbaaddeed
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbx]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR12]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR13]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR14]
        mov     [rax], rcx
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR15]
        mov     [rax], rcx
#endif

#ifdef _DEBUG
        // Call into some C++ code to validate the pop of the ExInfo.  We only do this in debug because we
        // have to spill all the preserved registers and then refill them after the call.

        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pRbx]
        mov     [rdi]                            , rbx
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     [rdi]                            , rbp
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pR12]
        mov     [rdi]                            , r12
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pR13]
        mov     [rdi]                            , r13
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pR14]
        mov     [rdi]                            , r14
        mov     rdi, [rdx + OFFSETOF__REGDISPLAY__pR15]
        mov     [rdi]                            , r15

        mov     rdi, [rsp]                                          // rdi <- Thread*
        mov     rsi, [rsp + locArg3]                                // rsi <- current ExInfo *
        mov     rdx, [rdx + OFFSETOF__REGDISPLAY__SP]               // rdx  <- resume SP value
        call    C_FUNC(RhpValidateExInfoPop)

        mov     rdx, [rsp + locArg2]          // rdx <- dispatch context
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbx]
        mov     rbx, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pRbp]
        mov     rbp, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR12]
        mov     r12, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR13]
        mov     r13, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR14]
        mov     r14, [rax]
        mov     rax, [rdx + OFFSETOF__REGDISPLAY__pR15]
        mov     r15, [rax]

#endif
        mov     rsi, [rsp + locThread]                              // rsi <- Thread*

        // We must unhijack the thread at this point because the section of stack where the hijack is applied
        // may go dead.  If it does, then the next time we try to unhijack the thread, it will corrupt the stack.
        INLINE_THREAD_UNHIJACK rsi, rdi, rcx                        // Thread in rsi, trashes rdi and rcx

        mov     rdi, [rsp + locArg3]                                // rdi <- current ExInfo *
        mov     rdx, [rdx + OFFSETOF__REGDISPLAY__SP]               // rdx <- resume SP value
        xor     ecx, ecx                                            // rcx <- 0

LOCAL_LABEL(Propagate_ExInfoLoop):
        mov     rdi, [rdi + OFFSETOF__ExInfo__m_pPrevExInfo]        // rdi <- next ExInfo
        cmp     rdi, rcx
        je      LOCAL_LABEL(Propagate_ExInfoLoopDone)                         // we're done if it's null
        cmp     rdi, rdx
        jl      LOCAL_LABEL(Propagate_ExInfoLoop)                             // keep looping if it's lower than the new SP

LOCAL_LABEL(Propagate_ExInfoLoopDone):
        mov     [rsi + OFFSETOF__Thread__m_pExInfoStackHead], rdi   // store the new head on the Thread

        // Switch to preemptive mode.
        mov     r8, [rsp + locArg4]
        mov     [rsi + OFFSETOF__Thread__m_pTransitionFrame], r8

        // reset RSP and RDI and jump to the propagation callback
        mov     rdi, [rsp + locArg0]                                // rdi <- callback context
        mov     rax, [rsp + locArg1]                                // rax <- callback
        mov     rsp, rdx                                            // reset the SP
        // Don't restore the stack pointer to exact same context. Leave the
        // return IP on the stack to let the unwinder work if the callback throws
        // an exception as opposed to failing fast.
        sub     rsp, 8
        jmp     rax

NESTED_END RhpCallPropagateExceptionCallback, _TEXT

#endif // FEATURE_OBJCMARSHAL
